package com.sk.compiler.processor;


import com.google.auto.service.AutoService;
import com.sk.api.annotations.Autowired;
import com.sk.compiler.info.NameInfo;
import com.sk.compiler.info.autowire.AutowireInfo;
import com.sk.compiler.info.autowire.BuilderUtils;
import com.sk.compiler.info.autowire.MethodInfo;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.tools.JavaFileObject;

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.sk.api.annotations.Autowired")
public class AutowireProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Map<String, AutowireInfo> map=handleInfo(annotations,roundEnv);
//        processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,map.toString());
        writeClass(map);
        return true;
    }

    private void writeClass(Map<String, AutowireInfo> map) {
        for (String key:map.keySet()){
            try {
                writeClass(key,map.get(key));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void writeClass(String key, AutowireInfo autowireInfo) throws IOException {
        NameInfo nameInfo=new NameInfo(key,"$Autowire");
        JavaFileObject sourceFile = processingEnv.getFiler()
                .createSourceFile(nameInfo.getNewFullName());
        Writer writer = sourceFile.openWriter();
        String content= BuilderUtils.buildAutowire(nameInfo,autowireInfo);
        writer.write(content);
        writer.close();
    }

    private Map<String, AutowireInfo> handleInfo(Set<? extends TypeElement> annotations,
                                                 RoundEnvironment roundEnv) {
        Map<String, AutowireInfo> map=new HashMap<>();
        for (TypeElement annotation:annotations){
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element element:elements){
                if(element instanceof VariableElement){
                    handleField(map, (VariableElement) element);
                }else {
                    handleMethod(map, (ExecutableElement) element);
                }
            }
        }
        return map;
    }

    private void handleMethod(Map<String, AutowireInfo> map, ExecutableElement element) {
        TypeElement clazz = (TypeElement) element.getEnclosingElement();
        String className = clazz.getQualifiedName().toString();
        AutowireInfo autowireInfo = getAutowireInfo(map, className);
        String methodName = element.getSimpleName().toString();
        MethodInfo methodInfo = new MethodInfo(methodName);
        List<? extends VariableElement> parameters = element.getParameters();
        for (VariableElement parameter:parameters){
            String key = parameter.getSimpleName().toString();
            String fullClass = parameter.asType().toString();
            methodInfo.addParamInfo(key,fullClass);
        }
        autowireInfo.addMethodInfo(methodInfo);
    }

    private void handleField(Map<String, AutowireInfo> map, VariableElement element) {
        TypeElement clazz = (TypeElement) element.getEnclosingElement();
        String className = clazz.getQualifiedName().toString();
        AutowireInfo autowireInfo = getAutowireInfo(map, className);
        Autowired autowired = element.getAnnotation(Autowired.class);
        String field=element.getSimpleName().toString();
        String key=autowired.value();
        if(key.equals(""))key=field;
        autowireInfo.addFieldInfo(field,key,element.asType().toString());
    }

    private AutowireInfo getAutowireInfo(Map<String, AutowireInfo> map, String className) {
        if(!map.containsKey(className)){
            map.put(className,new AutowireInfo());
        }
        return map.get(className);
    }

}